延續昨天類別之後,繼承是指類別與類別之間的關係,相關類別可以透過繼承來共享資料及行為。
我們可以舉生活上的例子來解釋繼承的概念,我們常常會將類似的事物分在同一類,比如:公車、跑車、休旅車、轎車雖然都外型不同、用途也有些差異,不過他們因為都還是具備車輛的基本特徵,比如都有品牌、乘載人數、車牌、具有交通、運輸用途...等等共同特徵,所以可以將他們都歸類為「車輛」。又比如說建築這個概念,可以是稻草屋、木屋、磚屋、鐵皮屋、糖果屋...等不同材質,但同樣都會具有牆壁、門窗、屋頂、地板、房間等共同特徵,基本都具有容納人類、遮風避雨的功能(不論效果好壞)、糖果屋可能另外具有引誘孩童的功能(誤)。
如果沒有繼承概念的話,以車子來說,那些車種都必須各自定義一次共同的特徵及行為。
而繼承就是可以讓我們將希望共享的特徵或行為建立在一個父類別,比如車輛,然後再建立子類別跑車、貨車、公車去繼承父類別這些共同的東西,各自依需要去客製化。
要建立繼承關係之前,必須先跟kotlin說我願意...被繼承,否則class是很矜持的,預設都是final,為封閉無法被繼承。
只有標明open關鍵字的類別才可以被繼承(open class:我準備好當爸爸了)
我們new一個新的class Car,使其可以被繼承,如:
open class Car
來建立繼承關係先:
class 子類別名稱():父類別名稱(){}
Car類別加上open關鍵字使其可被繼承,然後在同一個檔案(因為是練習就不另開新檔案囉)中建立 Taxi 類別名稱後方加上:Car()
,就是指繼承Car類別,成為其子類別,Car相對地成為Taxi的父類別(或超類別)。
//父類別Car
open class Car
//Car的子類別Bus
class Bus : Car(){}
先為父類別Car添加一些車輛會有的屬性跟函數:
簡單設置了車輛都要具有品牌、乘載量的屬性;發動、煞車的函數。
open class Car(val brand: String, val capacity: Int) {
fun start() {
println("車子發動了")
}
fun brake() {
println("車子停下了")
}
}
子類別 taxi可以繼承父類別這些已經設置好的屬性及函數:
父類別要求brand
和capacity
兩個引數,於是下面這個寫法就是將子類別兩個參數taxiBrand
、taxiCapacity
傳給父類別Car的建構函數。這樣的寫法,在Taxi實例化時才設定兩個引數。
//將taxiBrand、taxiCapacity傳給父類別Car的建構函數
class Taxi(taxiBrand: String, taxiCapacity: Int) : Car(taxiBrand, taxiCapacity) { }
//實例化一台taxi
fun main(){
val taxi = Taxi("Nissan",4)
}
當然也可以都先預設好或是預設其中一個,比如說車隊都是Toyota的車車:
class Taxi(taxiCapacity: Int) : Car("Toyota", taxiCapacity)
//實例化一台預設為Toyota的7人座taxi
fun main(){
val taxi = Taxi(7)
}
子類別透過句點運算子「.」使用父類別設好的函數,子類別不必再重新定義,如下:
taxi.start() //結果 : 車子發動了
若是需要特別為Taxi類別客製化 start() ,我們可以使用override關鍵字來覆寫,在覆寫父類別函數之前記得先把父類別的函數加上open關鍵字喔!!
open class Car(var brand: String, var capacity: Int) {
open fun start() {
println("車子發動了")
}
//不加open關鍵字是無法覆寫這個brake()函數的
fun brake() {
println("車子停下了")
}
}
class Taxi(taxiBrand: String, taxiCapacity: Int) : Car(taxiBrand, taxiCapacity) {
//覆寫start函數
override fun start() {
// 把這行槓掉,super是指父類Car,留著的話就等於呼叫一次父類的start()函數
// super.start()
println("計程車發動!") //覆寫的新內容
}
}
如果硬要覆寫brake()會出現下圖這樣的錯誤警告:
依照提示將open加上就沒問題囉。
除了覆寫外,當然也是可以自訂不同於父類別的新屬性及函數,比如說計程車都有起跳金額、也要跳表計費的功能:
open class Taxi(
protected open val initialCharge: Int,
taxiBrand:String,
taxiCapacity: Int
) : Car(taxiBrand, taxiCapacity) {
override fun start() {...}
//新增一跳表計費功能:每次計費前要先發動車子再開始跳表
fun chargeByMeter() {
start()
println("起跳金額:$initialCharge 元,現在開始跳表計費...")
}
}
//實例化
fun main() {
val taxi = Taxi(70, "Toyota",4)
taxi.chargeByMeter()
// 結果:
// 計程車發動!
// 起跳金額:70 元,現在開始跳表計費...
}
那假設現在推出一種新的豪華計程車Luxury Taxi,因為車款是高級車款,所以起跳價較高,但本質上仍是一種Taxi,我們可以考慮讓它繼承Taxi而不是Car,因為它有更接近Luxury Taxi需求的屬性跟函數(如起跳金額跟計費函數),並且為了要吸引目標客群,它也額外提供特殊功能的車種 :
class LuxuryTaxi(val special: String, initial: Int, brand: String, capacity: Int) : Taxi(initial, brand, capacity) {
fun specialServe(){
println("本車提供特別功能 : $special")
}
}
//實例化
fun main() {
val luxuryTaxi = LuxuryTaxi("bulletproof" , 500 , "Benz", 4)
luxuryTaxi.chargeByMeter()
luxuryTaxi.specialServe()
//結果:
//計程車發動!
//起跳金額:500 元,現在開始跳表計費...
//本車提供特別功能 : bulletproof
}
現在我們有三個類別,雖然顯而易見,但是可以玩一下類型檢測,可以讓我們更理解繼承關係中父、子類別之間的糾葛:
// is 的用法 :
物件 is 類型 // 物件跟類別的位置不可對調
準備三位物件選手 :
val car = Car(...)
val taxi= Taxi(...)
val luxuryTaxi:LuxuryTaxi = LuxuryTaxi(...)
//類型檢查
//luxuryTaxi類型檢查
println(luxuryTaxi is LuxuryTaxi ) //true
println(luxuryTaxi is Taxi ) //true
println(luxuryTaxi is Car ) //true
println(luxuryTaxi is Any) //true
//taxi類型檢查
println(taxi is Taxi) //true
println(taxi is Car) //true
println(taxi is LuxuryTaxi) //false
println(taxi is Any) //true
//car類型檢查
println(car is Car ) //true
println(car is Taxi ) //false
println(car is LuxuryTaxi ) //false
println(car is Any) //true
由這個檢測知道:
需注意若使用條件判斷(如:when)檢測類型的話,分支順序將會影響結果,因為它是一分支順序檢測,然後返回第一個結果為true的分支。
今天就這樣,明天見~